﻿//*** Guidance generated code ***//
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Web.Mvc;
using System.Web.Routing;

using ProjectBase.Core;
using ProjectBase.Core.Service;
using ProjectBase.Core.Web;
using $safeprojectname$;

namespace $safeprojectname$.Controllers
{
    /// <summary>
    ///     Base EntityController class for int ID entities
    /// </summary>
    /// <typeparam name = "TEntity">Entity with int ID to be moved between Controller and View</typeparam>
    /// <typeparam name = "TModel">Type of the object available via Model property - lazily (when firstly needed) instantiated</typeparam>
    /// <typeparam name = "TFacade">Entity (Read and Write) Facade available via Facade property  - lazily (when firstly needed) instantiated</typeparam>
    /// <typeparam name = "TFilter"></typeparam>
    public abstract class EntityController<TEntity, TFilter, TModel, TFacade>
        : EntityController<TEntity, int, TFilter, TModel, TFacade>
        where TEntity : class, IPersistentObjectWithTypedId<int>
        where TFilter : class, IFilter<int>
        where TModel : class, IEditModel<TEntity, int, TFilter>
        where TFacade : class, IEntityFacade<TEntity, int, TFilter> { }

    /// <summary>
    ///     Controller base for read - write operations.
    ///     Extends the ReadController with CRUD write operations
    ///     (New - Add, Edit - Update, Delete)
    /// </summary>
    /// <typeparam name = "TEntity">Entity with int ID to be moved between Controller and View</typeparam>
    /// <typeparam name = "TID">type of the ID (string is not supported in the base EntityController implemenatation).
    ///     Only the ValueTypes decimal, int, short, long Guid are supported in default implementation</typeparam>
    /// <typeparam name = "TModel">Type of the object available via Model property - lazily (when firstly needed) instantiated</typeparam>
    /// <typeparam name = "TFacade">Entity (Read and Write) Facade available via Facade property  - lazily (when firstly needed) instantiated</typeparam>
    /// <typeparam name = "TFilter"></typeparam>
    public abstract class EntityController<TEntity, TID, TFilter, TModel, TFacade>
        : ReadController<TEntity, TID, TFilter, TModel, TFacade>
        , IHaveEntityBinder
        where TID : struct
        where TEntity : class, IPersistentObjectWithTypedId<TID>
        where TFilter : class, IFilter<TID>
        where TModel : class, IEditModel<TEntity, TID, TFilter>
        where TFacade : class, IEntityFacade<TEntity, TID, TFilter>
    {
        #region New
        /// <summary>
        ///     Creates the model to be ready for new Entity
        ///     If overriden OnBeforeNew ->
        ///     some usefull settings to Entity properties can be done...
        /// </summary>
        /// <returns></returns>
        public virtual ActionResult New()
        {
            Model.Item = Facade.CreateNew();

            OnBeforeNew();
            OnBeforeAny();

            return View(Constants.Actions.Detail, Model);
        }

        /// <summary>
        ///     Stores the newly created entity to the storage
        ///     if everything is OK. Redirects to the Detail screen
        /// </summary>
        /// <returns></returns>
        [Transaction]
        [HttpPost]
        [ValidateAntiForgeryToken]
        public virtual ActionResult New(TEntity item)
        {
            Model.Item = item;

            OnBeforeNew(); // !!! even if session dies, this can proceed!!!
            OnBeforeAny();

            Model.Item = Facade.Add(Model.Item);
            if (!Messages.ContainsError()) // Business rules are fullfilled
            {
                return Request.IsSubmitButton(Constants.Actions.Special.SaveAndClose)
                    ? RedirectToAction(Constants.Actions.EntityDefault)
                    : RedirectToAction(Constants.Actions.Detail, new { id = Model.Item.ID });
            }
            // one more try for user
            return View(Constants.Actions.Detail, Model);
        }
        #endregion New

        #region Edit
        /// <summary>
        ///     Prepares Model for edtiting screen
        /// </summary>
        /// <param name = "id"></param>
        /// <returns></returns>
        public virtual ActionResult Edit(TID? id)
        {
            if (id.IsNull())
            {
                return RedirectToAction(Constants.Actions.Find);
            }

            Model.Item = Facade.GetById(id.Value);

            if (Model.Item.IsNull())
            {
                this.PublishWarning(Constants.Messages.EntityNotFound2
                    , ControllerName.GetLocalized()
                    , id.Value);
                return RedirectToAction(Constants.Actions.Find);
            }

            if (!Facade.CanBeEdited(Model.Item))
            {
                this.PublishError(Constants.Messages.CannotBeEdited2, id.ToDisplay(), Model.Item.ToDisplay());
                return RedirectToAction(Constants.Actions.Detail, new { id });
            }

            OnBeforeEdit(id.Value);
            OnBeforeAny();

            return View(Constants.Actions.Detail, Model);
        }

        /// <summary>
        ///     Process changed entity to be saved (updated)
        ///     if all rules (mostly the business) are fulfiled
        /// </summary>
        /// <param name = "item">entity</param>
        /// <param name = "id">unique ID of entity to be edited</param>
        /// <returns></returns>
        [Transaction]
        [HttpPost]
        [ValidateAntiForgeryToken]
        public virtual ActionResult Edit(TEntity item, TID? id)
        {
            if (item.IsNull())
            {
                this.PublishError("Entity with ID {0} was not found, cannot be edited", id);
                return RedirectToAction(Constants.Actions.Find);
            }

            Model.Item = item;

            if (!Facade.CanBeEdited(Model.Item))
            {
                this.PublishError(Constants.Messages.CannotBeEdited2, Model.Item.IDToDisplay, Model.Item.ToDisplay());
                return RedirectToAction(Constants.Actions.Detail, new { Model.Item.ID });
            }

            OnBeforeEdit(Model.Item.ID);
            OnBeforeAny();

            Model.Item = Facade.Update(Model.Item);
            if (!Messages.ContainsError()) // Business rules are fullfilled
            {
                return Request.IsSubmitButton(Constants.Actions.Special.SaveAndClose)
                    ? RedirectToAction(Constants.Actions.EntityDefault)
                    : RedirectToAction(Constants.Actions.Detail, new { Model.Item.ID });
            }

            if (Messages.Any(message => message.Template.Equals(Constants.Messages.EntityNotExists2)))
            {
                OnBeforeNew();
            }

            // one more try for user
            return View(Constants.Actions.Detail, Model);
        }
        #endregion Edit

        #region Delete
        /// <summary>
        ///     Removes the Entity from the storage and redicrects to the List (Default)
        /// </summary>
        /// <param name = "id"></param>
        /// <returns></returns>
        [Transaction]
        [HttpPost]
        [ValidateAntiForgeryToken]
        public virtual ActionResult Delete(TID? id)
        {
            if (id.IsNull())
            {
                return RedirectToAction(Constants.Actions.Find);
            }

            var item = Facade.GetById(id.Value);
            if (item.IsNull())
            {
                this.PublishWarning(Constants.Messages.EntityNotFound2
                    , ControllerName.GetLocalized()
                    , id.Value);
                return RedirectToAction(Constants.Actions.Find);
            }

            //if (!Facade.CanBeDeleted(item))
            //{
            //    this.PublishError(Constants.Messages.CannotBeDeleted2, id.ToDisplay(), item.ToDisplay());
            //    return RedirectToAction(Constants.Actions.Detail, new { id });
            //}

            Facade.Delete(item);

            if (Model.Messages.ContainsError())
            {
                return RedirectToAction(Constants.Actions.Detail, new { id });
            }
            return RedirectToAction(Constants.Actions.EntityDefault, new { id = default(int?) });
        }

        [Transaction]
        [HttpPost]
        [ValidateAntiForgeryToken]
        public virtual ActionResult DeleteAll()
        {
            OnBeforeList();
            OnBeforeAny();

            Model.Filter = OnBindFilter();

            if (Model.Messages.ContainsError())
            {
                return RedirectToAction(Constants.Actions.Find);
            }

            if (Facade.DeleteAll(Model.Filter))
            {
                Model.Filter = ClearFilter();
            }

            return RedirectToAction(Constants.Actions.List);
        }
        #endregion Delete

        #region Import
        /// <summary>
        ///     Creates the model to be ready for new Entity
        ///     If overriden OnBeforeNew ->
        ///     some usefull settings to Entity properties can be done...
        /// </summary>
        /// <returns></returns>
        public virtual ActionResult Import()
        {
            OnBeforeImport();
            OnBeforeAny();

            return View(Constants.Actions.ExportImport.Import, Model);
        }

        /// <summary>
        ///     Stores the newly created entity to the storage
        ///     if everything is OK. Redirects to the Detail screen
        /// </summary>
        /// <returns></returns>
        [Transaction]
        [HttpPost]
        [ValidateAntiForgeryToken]
        public virtual ActionResult ImportFile()
        {
            OnBeforeAny();

            var model = Factory.CreateInstance<IImportModel>();

            var success = BindModel(model);

            if (success) // if there are any problems with binding, go back to detail
            {
                if (Facade.Import(model.DataSource))
                {
                    return RedirectToAction(Constants.Actions.List);
                }
            }
            // one more try for user
            return RedirectToAction(Constants.Actions.ExportImport.Import);
        }
        #endregion Import

        #region Cancel Edit
        /// <summary>
        ///     This action should be used On EDIT
        ///     This action always provides direct Redirection to the List.
        /// </summary>
        public virtual ActionResult CancelEdit(TID? id)
        {
            return RedirectToAction(Constants.Actions.Detail, new { id });
        }
        #endregion Cancel Clear

        #region IHaveEntityBinder
        /// <summary>
        ///     Calls Facade to BindEntity(Model.Item, ValueProvider) with a FORM data.
        /// </summary>
        /// <returns></returns>
        T IHaveEntityBinder.BindEntity<T>(T model, IValueProvider valueProvider)
        {
            return BindEntity(model, valueProvider);
        }
        /// <summary>
        ///     Calls Facade to BindEntity(Model.Item, ValueProvider) with a FORM data.
        /// </summary>
        /// <returns></returns>
        protected virtual T BindEntity<T>(T model, IValueProvider valueProvider)
           where T : class, IPersistentObject
        {
            return Facade.BindEntity(model as TEntity, valueProvider.ToContainer()) as T;
        }
        #endregion IHaveEntityBinder

        #region protected OnBefore Actions
        /// <summary>
        ///     Sets Actions and buttons available on an Edit screen
        /// </summary>
        protected virtual void OnBeforeEdit(TID id)
        {
            this.AddAction(Constants.Actions.New, Url) // sets the basic Actions
                .AddAction(Constants.Actions.Find, Url)
                .AddAction(Constants.Actions.List, Url);

            // submit button for Form with id="Constants.WebControls.MainFormId"
            Model.MasterModel.ButtonSubmit = new ButtonItem(Constants.Actions.Edit)
            {
                FormController = ControllerName,
                FormRouteValues = new RouteValueDictionary { { Constants.Common.RouteValuesId, id } },
                Text = Constants.Actions.NoView.Update,
            };

            // Cancel button
            this.AddButton(Constants.Actions.NoView.CancelEdit, Url, new RouteValueDictionary { { Constants.Common.RouteValuesId, id } });
            // Save and Close button
            Model.MasterModel.ButtonsModel.Buttons.Add
            (
               new ButtonItem(Constants.Actions.Special.SaveAndClose) { FormIdForJS = Constants.WebControls.MainFormId, }
            );

            Model.ValidationRulesJS = Url.Action(Constants.Actions.Special.ValidationRules
                , new { filter = Constants.Actions.NoView.Update });
        }

        /// <summary>
        ///     Sets Actions and buttons available on a New screen
        /// </summary>
        protected virtual void OnBeforeNew()
        {
            this.AddAction(Constants.Actions.New, Url) // sets the basic Actions
                .AddAction(Constants.Actions.Find, Url)
                .AddAction(Constants.Actions.List, Url);

            Model.MasterModel.CurrentAction = Constants.Actions.New;

            // submit button for Form with id="Constants.WebControls.MainFormId"
            Model.MasterModel.ButtonSubmit = new ButtonItem(Constants.Actions.NoView.Add + Constants.Actions.New)
            {
                FormAction = Constants.Actions.New,
                FormController = ControllerName,
            };
            // Cancel button
            this.AddButton(Constants.Actions.NoView.CancelDefault, Url);
            // Save and Close button
            Model.MasterModel.ButtonsModel.Buttons.Add
            (
               new ButtonItem(Constants.Actions.Special.SaveAndClose) { FormIdForJS = Constants.WebControls.MainFormId, }
            );

            Model.ValidationRulesJS = Url.Action(Constants.Actions.Special.ValidationRules
                , new { filter = Constants.Actions.NoView.Add });
        }

        protected override void OnBeforeFind()
        {
            base.OnBeforeFind();
            this.InsertAction(Constants.Actions.New, Url, index: 0)
                .AddAction(Constants.Actions.ExportImport.Import, Url, text: Constants.Actions.ExportImport.GoToImport);
        }

        protected override void OnBeforeList()
        {
            base.OnBeforeList();
            this.InsertAction(Constants.Actions.New, Url, index: 0)
                .AddAction(Constants.Actions.ExportImport.Import, Url, text: Constants.Actions.ExportImport.GoToImport);

            Model.MasterModel.ButtonsModel.Buttons.Add(new ButtonItem(Constants.Actions.NoView.DeleteAll)
            {
                FormAction = Constants.Actions.NoView.DeleteAll,
                FormController = ControllerName,
                Title = Constants.Actions.NoView.DeleteAll + "Title",
            });

            Model.ListModel.RowRenderMode = RowRenderMode.Editable;
        }

        protected override void OnBeforeDetail(TID id)
        {
            base.OnBeforeDetail(id);

            this.InsertAction(Constants.Actions.New, Url, index: 0);

            if (Facade.CanBeDeleted(Model.Item))
            {
                this.InsertButton(0, Constants.Actions.NoView.Delete, Url, new RouteValueDictionary { { Constants.Common.RouteValuesId, id } });
            }
            if (Facade.CanBeEdited(Model.Item))
            {
                this.InsertButton(0, Constants.Actions.Edit, Url, new RouteValueDictionary { { Constants.Common.RouteValuesId, id } });
            }
        }

        /// <summary>
        ///     Sets Actions and buttons available on a New screen
        /// </summary>
        protected virtual void OnBeforeImport()
        {
            this.AddAction(Constants.Actions.New, Url) // sets the basic Actions
                .AddAction(Constants.Actions.Find, Url)
                .AddAction(Constants.Actions.List, Url);

            // submit button for Form with id="Constants.WebControls.MainFormId"
            Model.MasterModel.ButtonSubmit = new ButtonItem(Constants.Actions.ExportImport.Import)
            {
                FormAction = Constants.Actions.NoView.ImportFile,
                FormController = ControllerName,
            };

            this.AddButton(Constants.Actions.NoView.CancelDefault, Url, text: Constants.Actions.NoView.CancelDetail);
        }
        #endregion protected OnBefore Actions
    }
}